/**
 * @Author: aesoper
 * @Description:
 * @File:  memory
 * @Version: 1.0.0
 * @Date: 2020/6/4 19:24
 */

package memory

import (
	"bytes"
	"encoding/gob"
	"errors"
	"gitee.com/aesoper/cache/factory"
	"github.com/patrickmn/go-cache"
	"sync"
	"time"
)

type (
	CaChe struct {
		client *cache.Cache
		waiter sync.WaitGroup
		mux    sync.RWMutex
		cfg    Option
	}
	Option struct {
		DefaultExpiration time.Duration `mapstructure:"defaultExpiration"`
		CleanupInterval   time.Duration `mapstructure:"cleanupInterval"`
	}
)

func (c *CaChe) Get(key string) ([]byte, error) {
	c.mux.RLock()
	defer c.mux.RUnlock()

	result, ok := c.client.Get(key)
	if !ok {
		return nil, errors.New("cache not found")
	}


	var buf bytes.Buffer
	encoder := gob.NewEncoder(&buf)
	if err := encoder.Encode(result); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

func (c *CaChe) Put(key string, val interface{}, timeout time.Duration) error {
	c.mux.Lock()
	defer c.mux.Unlock()

	if _, ok := c.client.Get(key); ok {
		return c.client.Replace(key, val, timeout)
	}
	return c.client.Add(key, val, timeout)
}

func (c *CaChe) Delete(key string) error {
	c.mux.Lock()
	defer c.mux.Unlock()

	c.client.Delete(key)
	return nil
}

func (c *CaChe) Incr(key string) error {
	c.mux.Lock()
	defer c.mux.Unlock()

	return c.client.Increment(key, 1)
}

func (c *CaChe) Decr(key string) error {
	c.mux.Lock()
	defer c.mux.Unlock()

	return c.client.Decrement(key, 1)
}

func (c *CaChe) IsExist(key string) bool {
	c.mux.RLock()
	defer c.mux.RUnlock()

	_, ok := c.client.Get(key)
	return ok
}

func (c *CaChe) ClearAll() error {
	c.mux.Lock()
	defer c.mux.Unlock()

	c.client.Flush()
	return nil
}

func (c *CaChe) StartAndConfigure(ops ...factory.CacheOption) error {
	for _, op := range ops {
		op(c)
	}
	c.client = cache.New(5*time.Minute, 10*time.Minute)
	return nil
}

func (c *CaChe) Close() error {
	if c.client != nil {
		c.client = nil
	}
	return nil
}

func init() {
	factory.Register("memory", NewCache)
}

func NewCache() factory.Cache {
	return &CaChe{
		cfg: Option{
			DefaultExpiration: 5 * time.Minute,
			CleanupInterval:   10 * time.Minute,
		},
	}
}

func WithMemoryOption(op Option) factory.CacheOption {
	return func(c factory.Cache) {
		if cache, ok := c.(*CaChe); ok {
			if op.CleanupInterval == 0 {
				op.CleanupInterval = 10 * time.Minute
			}
			if op.DefaultExpiration == 0 {
				op.DefaultExpiration = 5 * time.Minute
			}
			cache.cfg = op
		}
	}
}
